376. 摆动序列
376. 摆动序列
Similar Question
Solution Tips
错误方案: 回溯
var wiggleMaxLength = function(nums) {
// 有点类似于单调的问题
// 直接算出差值序列就好, 然后 count 一下统计即可
// if (nums.length === 1) {
// return 1;
// };
// if (nums.length === 2) {
// return nums[0] === nums[1] ? 1 : 2;
// }
// let count = 0;
let res = 0;
// for (let i = 2; i < nums.length; i++) {
// if ((nums[i] - nums[i - 1]) * (nums[i - 1] - nums[i - 2]) < 0) {
// count++;
// res = Math.max(res, count)
// }
// else {
// // 同向了, 清零
// count = 0;
// }
// }
// 子序列, 还得贪心的去寻找下一个符合摆动方向的数字?
// 从每个数字出发, 去贪心的构造一次. 这样能避免 1 17 99 98 100 97
// 我怎么感觉可以用回溯法来做, 就是二元思路, 要不要带上 17
// 为什么这里贪心的构造就可以得到最优解呢?
// 按找回溯法走吧, 暴力也得写出来才行
backtracking([], 0)
function backtracking(chosen, i) {
if (i === nums.length) {
return;
}
// 目前的情况能构造出的最大长度, 不会超过 res 可提前返回
if (chosen.length + nums.length - i <= res) {
return;
}
const n = chosen.length;
if (n < 2) {
// 只要不相等就可以选择
if (nums[i] !== chosen[n - 1]) {
chosen.push(nums[i]);
res = Math.max(res, chosen.length)
backtracking(chosen, i + 1);
// 不选择也是ok的
chosen.pop();
backtracking(chosen, i + 1);
return;
}
}
const lastTwo = chosen[n - 1] - chosen[n - 2];
if ((nums[i] - chosen[n - 1]) * lastTwo < 0) {
// 二元思路, 子序列, 越是前面的数, 越是有可能构造出最长的序列, 所以需要提前返回一下 dfs 构造
chosen.push(nums[i]);
res = Math.max(res, chosen.length)
backtracking(chosen, i + 1);
chosen.pop();
}
// 没有选择的 case
backtracking(chosen, i + 1);
}
// 暴力法超时了, 还是得从局部到全局. 对于这个子序列而言, 最长的是这么多, 要么选就是这么多, 要么不选整个跳过
// 有重复计算的地方, 不论 17 选还是不选, 后面的连续序列中最长的都是一样的
// 而17如果能选, 那么一定比不选要长
// 要怎么保证去除掉某个数之后, 不会构成更长的子序列呢?
return res;
};
console.log(wiggleMaxLength([1,17,5,10,13,15,10,5,16,8]))
从回溯的思路去思考问题, 很容易陷入动态规划与回溯结合的思路中, 但是却写不出代码
方案一: 贪心
观察这个序列可以发现,我们不断地交错选择「峰」与「谷」,可以使得该序列尽可能长。因为剔除掉一个峰或者谷之后, 选择的过渡元素只能成为被剔除的峰或谷, 不可能带来更多的峰或谷
这样,我们只需要统计该序列中「峰」与「谷」的数量即可(注意序列两端的数也是「峰」或「谷」),但需要注意处理相邻的相同元素。
在实际代码中,我们记录当前序列的上升下降趋势。每次加入一个新元素时,用新的上升下降趋势与之前对比,如果出现了「峰」或「谷」,答案加一,并更新当前序列的上升下降趋势。
var wiggleMaxLength = function(nums) {
const n = nums.length;
if (n < 2) {
return n;
}
let prevdiff = nums[1] - nums[0];
let ret = prevdiff !== 0 ? 2 : 1;
for (let i = 2; i < n; i++) {
const diff = nums[i] - nums[i - 1];
if ((diff > 0 && prevdiff <= 0) || (diff < 0 && prevdiff >= 0)) {
ret++;
prevdiff = diff;
}
}
return ret;
};
方案三: Dp
分析
可以通过删除元素构造,删除得最少的子序列最长
初始化分析: 只有一个元素的序列也是摆动序列. 有两个元素的序列,只要两个元素不相等,就是摆动序列,两个元素的相对大小决定了摆动的方向
转换视角: 如果波动相同,就和上一轮的最优情况一致,否则就等于上一轮长度的 +1
实现
var wiggleMaxLength = function (nums) {
let n = nums.length;
if (n < 2) {
return n;
}
let wiggle = ''
let current = ''
let T = []
T[0] = 1
for (let i = 1; i < n; i++) {
if (nums[i] > nums[i - 1]) {
current = 'up'
} else if (nums[i] < nums[i - 1]) {
current = 'down'
} else {
// current = 'equal'
// 不改变current的值就正好
}
// 如果波动相同,最优情况就是T[i - 1],如果波动不相同就+1
T[i] = current === wiggle ? T[i - 1] : T[i - 1] + 1
wiggle = current
}
return T[n-1]
};
var wiggleMaxLength = function (nums) {
let n = nums.length;
if (n < 2) {
return n;
}
let up = 1;
let down = 1;
for (let i = 1; i < n; i++) {
if (nums[i] > nums[i - 1]) {
up = down + 1;
}
if (nums[i] < nums[i - 1]) {
down = up + 1;
}
}
return Math.max(up, down);
};